package com.tools.commons.aspect;

import cn.hutool.extra.servlet.ServletUtil;
import com.tools.commons.utils.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;

/**
 * @author wangminggen
 */
@Aspect
@Slf4j
public class LogAspect {
    @Pointcut("@annotation(com.tools.commons.annotation.Log)")
    public void log() {
    }

    @After("log()")
    public void doAfter(JoinPoint joinPoint) {
        log.info("-------------------------------  End  --------------------------");
    }

    @Before("log()")
    public void doBefore(JoinPoint joinPoint) {
        log.info("------------------------------- start --------------------------");
    }

    @Around("log()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        printRequestLog(joinPoint);
        long start = System.currentTimeMillis();
        Object returnObj = joinPoint.proceed();

        printResponseLog(returnObj);

        log.info("cost time [{}]", System.currentTimeMillis() - start);
        return returnObj;
    }

    private void printRequestLog(JoinPoint joinPoint) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(requestAttributes).getRequest();

        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();

        log.info("Request URL: [ {} ]", request.getRequestURL().toString());
        log.info("Request method: [ {} ]", request.getMethod());
        log.info("Request IP: [ {} ]", ServletUtil.getClientIP(request));
        log.info("Request Class and Method: [ {}#{} ]", className, methodName);

        boolean shouldNotLog = false;
        if (args != null) {
            for (Object arg : args) {
                if (arg == null ||
                        arg instanceof HttpServletRequest ||
                        arg instanceof HttpServletResponse ||
                        arg instanceof MultipartFile ||
                        arg.getClass().isAssignableFrom(MultipartFile[].class)) {
                    shouldNotLog = true;
                    break;
                }
            }
        }

        if (!shouldNotLog) {
            String requestBody = JsonUtil.obj2String(args);
            log.info("Request Parameters: [ {} ] , {}#{}", requestBody, className, methodName);
        }
    }

    private void printResponseLog(Object returnObj) {
        if (log.isDebugEnabled()) {
            String returnData = "";

            if (returnObj != null) {
                if (returnObj instanceof ResponseEntity) {
                    ResponseEntity responseEntity = (ResponseEntity) returnObj;
                    if (responseEntity.getBody() instanceof Resource) {
                        returnData = "[ BINARY DATA ]";
                    } else {
                        returnData = toString(Objects.requireNonNull(responseEntity.getBody()));
                    }
                } else {
                    returnData = toString(returnObj);
                }

            }
            log.info("Response: [{}]", returnData);
        }
    }

    @NonNull
    private String toString(@NonNull Object obj) {
        Assert.notNull(obj, "Return object must not be null");

        String toString = "";
        if (obj.getClass().isAssignableFrom(byte[].class) && obj instanceof Resource) {
            toString = "[ BINARY DATA ]";
        } else {
            toString = JsonUtil.obj2String(obj);
        }
        return toString;
    }
}
